#include <algorithm>
#include <sstream>

#include "HtmlOutput.h"
#include "Utils.h"

using namespace std;

namespace uav
{
namespace test
{

namespace
{
	void
	strreplace(string &value, char search, const string &replace)
	{
		string::size_type idx = 0;
		while((idx = value.find(search, idx)) != string::npos)
		{
			value.replace(idx, 1, replace);
			idx += replace.size();
		}
	}

	string
	escape(string value)
	{
		strreplace(value, '&', "&amp;");
		strreplace(value, '<', "&lt;");
		strreplace(value, '>', "&gt;");
		strreplace(value, '"', "&quot;");
		strreplace(value, '\'', "&#39;");
		return value;
	}

	void
	header(ostream& os, string name)
	{
		if (!name.empty())
			name += " ";
		name = escape(name);
		os <<
			"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
			"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
			"<head>\n"
			"  <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n"
			"  <meta name=\"generator\" content=\"CppTest - http://cpptest.sourceforge.net\" />\n"
			"  \n"
			"  <title>" << name << "Unit Tests Results</title>\n"
			"  \n"
			"  <style type=\"text/css\" media=\"screen\">\n"
			"    <!--\n"
			"    hr  {\n"
			"      width: 100%;\n"
			"      border-width: 0px;\n"
			"      height: 1px;\n"
			"      color: #cccccc;\n"
			"      background-color: #cccccc;\n"
			"      padding: 0px;\n"
			"    }\n"
			"    \n"
			"    table {\n"
			"      width:100%;\n"
			"      border-collapse:separate;\n"
			"      border-spacing: 2px;\n"
			"      border:0px;\n"
			"    }\n"
			"    tr {\n"
			"      margin:0px;\n"
			"      padding:0px;\n"
			"    }\n"
			"    td {\n"
			"      margin:0px;\n"
			"      padding:1px;\n"
			"    }\n"
			"    .table_summary {\n"
			"    }\n"
			"    .table_suites {\n"
			"    }\n"
			"    .table_suite {\n"
			"    }\n"
			"    .table_result {\n"
			"      margin: 0px 0px 1em 0px;\n"
			"    }\n"
			"    .tablecell_title {\n"
			"      background-color: #a5cef7;\n"
			"      font-weight: bold;\n"
			"    }\n"
			"    \n"
			"    .tablecell_success {\n"
			"      background-color: #efefe7;\n"
			"    }\n"
			"    \n"
			"    .tablecell_error {\n"
			"      color: #ff0808;\n"
			"      background-color: #efefe7;\n"
			"      font-weight: bold;\n"
			"    }\n"
			"    p.spaced {\n"
			"      margin: 0px;\n"
			"      padding: 1em 0px 2em 0px;\n"
			"    }\n"
			"    p.unspaced {\n"
			"      margin: 0px;\n"
			"      padding: 0px 0px 2em 0px;\n"
			"    }\n"
			"    -->\n"
			"  </style>\n"
			"</head>\n"
			"\n"
			"<body>\n"
			"\n"
			"<h1><a name=\"top\"></a>" << name << "Unit Tests Results</h1>\n"
			"\n"
			"<div style=\"text-align:right\">\n"
			"Designed by <a href=\"http://cpptest.sourceforge.net\">CppTest</a>\n"
			"</div>\n"
			"<hr />\n"
			"\n";
	}

	void
	footer(ostream& os)
	{
		os <<
			"\n"
			"<p>\n"
			"  <a href=\"http://validator.w3.org/#validate-by-upload\">\n"
			"    Valid XHTML 1.0 Strict\n"
			"  </a>\n"
			"</p>\n"
			"</body>\n</html>\n";
	}

	void
	back_ref(ostream& os, const string& ref, bool prepend_newline = true)
	{

		os	<< "<p class=\"" << (prepend_newline ? "spaced" : "unspaced") << "\"><a href=\"#" << ref
			<< "\">Back to " << escape(ref) << "</a>\n</p>\n";
	}

	void
	sub_title(ostream& os, const string& title, int size)
	{
		ostringstream h;
		h << "h" << size;
		os << "<" << h.str() << ">" << escape(title) << "</" << h.str() << ">\n";
	}

	void
	sub_title(ostream& os, const string& title, int size, const string& mark)
	{
		ostringstream h;
		h << "h" << size;
		os << "<" << h.str() << "><a name=\"" << mark << "\"></a>" << escape(title) << "</" << h.str() << ">\n";
	}

	enum ClassTableType { TableClass_Summary, TableClass_Suites, TableClass_Suite, TableClass_Result };

	void
	table_header(ostream& os, ClassTableType type, const string &summary = "")
	{
		static const char* class_tabletypes[] = { "summary", "suites", "suite", "result" };

		os << "<table summary=\"" << escape(summary) << "\" class=\"table_" << class_tabletypes[type] << "\">\n";
	}

	void
	table_footer(ostream& os)
	{
		os << "</table>\n";
	}

	void
	table_tr_header(ostream& os)
	{
		os << "  <tr>\n";
	}

	void
	table_tr_footer(ostream& os)
	{
		os << "  </tr>\n";
	}

	enum ClassType { Title, Success, Error };

	void
	table_entry(ostream& os, ClassType type, const string& s,
				int width = 0, const string& link = "")
	{
		static const char* class_types[] = { "title", "success", "error" };

		os << "    <td";
		if (width)
			os << " style=\"width:" << width << "%\"";
		if (!link.empty())
			os << " class=\"tablecell_" << class_types[type] << "\"><a href=\"#" << link << "\">" << escape(s) << "</a>";
		else
			os << " class=\"tablecell_" << class_types[type] << "\">" << escape(s);
		os << "</td>\n";
	}

} // anonymous namespace

// Test suite table
//
struct HtmlOutput::SuiteRow
{
	ostream& _os;
	SuiteRow(ostream& os) : _os(os) {}
	void operator()(const SuiteInfo& si)
	{
		ClassType 		type(si._errors > 0 ? Error : Success);
		ostringstream 	ss;

		table_tr_header(_os);
		  table_entry(_os, type, si._name, 0, si._name);
		  ss.str(""), ss << si._tests.size();
		  table_entry(_os, type, ss.str(), 10);
		  ss.str(""), ss << si._errors;
		  table_entry(_os, type, ss.str(), 10);
		  ss.str(""),  ss << correct(si._tests.size(), si._errors) << "%";
		  table_entry(_os, type, ss.str(), 10);
		  ss.str(""), ss << si._time;
		  table_entry(_os, type, ss.str(), 10);
		table_tr_footer(_os);
	}
};

// Individual tests tables, tests
//
struct HtmlOutput::TestRow
{
	bool		_incl_ok_tests;
	ostream& 	_os;
	TestRow(ostream& os, bool incl_ok_tests)
		: _incl_ok_tests(incl_ok_tests), _os(os) {}
	void operator()(const TestInfo& ti)
	{
		if (!ti._success || _incl_ok_tests)
		{
			string			link = ti._success ? string(""):
								   ti._sources.front().suite() + "_" + ti._name;
			ClassType 		type(ti._success ? Success : Error);
			ostringstream 	ss;

			table_tr_header(_os);
			  table_entry(_os, type, ti._name, 0, link);
			  ss.str(""),  ss << ti._sources.size();
			  table_entry(_os, type, ss.str());
			  table_entry(_os, type, ti._success ? "true" : "false");
			  ss.str(""), ss << ti._time;
			  table_entry(_os, type, ss.str());
			table_tr_footer(_os);
		}
	}
};

// Individual tests tables, header
//
struct HtmlOutput::TestSuiteRow
{
	bool		_incl_ok_tests;
	ostream& 	_os;
	TestSuiteRow(ostream& os, bool incl_ok_tests)
		: _incl_ok_tests(incl_ok_tests), _os(os) {}
	void operator()(const SuiteInfo& si)
	{
		ostringstream ss;

		sub_title(_os, "Suite: " + si._name, 3, si._name);
		table_header(_os, TableClass_Suite, "Details for suite " + si._name);
		  table_tr_header(_os);
		    table_entry(_os, Title, "Name");
		    table_entry(_os, Title, "Errors", 10);
		    table_entry(_os, Title, "Success", 10);
		    table_entry(_os, Title, "Time (s)", 10);
		  table_tr_footer(_os);
		  for_each(si._tests.begin(), si._tests.end(),
				   TestRow(_os, _incl_ok_tests));
		table_footer(_os);
		back_ref(_os, "top");
	}
};

// Individual tests result tables
//
struct HtmlOutput::TestResult
{
	ostream& _os;
	TestResult(ostream& os) : _os(os) {}
	void operator()(const Source& s)
	{
		const int TitleSize = 15;

		ostringstream ss;

		table_header(_os, TableClass_Result, "Test Failure");
		  table_tr_header(_os);
			table_entry(_os, Title, "Test", TitleSize);
			table_entry(_os, Success, s.suite() + "::" + s.test());
		  table_tr_footer(_os);
		  table_tr_header(_os);
			table_entry(_os, Title, "File", TitleSize);
			ss << s.file() << ":" << s.line();
			table_entry(_os, Success, ss.str());
		  table_tr_footer(_os);
		  table_tr_header(_os);
			table_entry(_os, Title, "Message", TitleSize);
			table_entry(_os, Success, s.message());
		  table_tr_footer(_os);
		table_footer(_os);
	}
};

// All tests result tables
//
struct HtmlOutput::TestResultAll
{
	ostream& _os;
	TestResultAll(ostream& os) : _os(os) {}
	void operator()(const TestInfo& ti)
	{
		if (!ti._success)
		{
			const string& suite = ti._sources.front().suite();

			sub_title(_os, suite + "::" + ti._name, 3, suite + "_" + ti._name);
			for_each(ti._sources.begin(), ti._sources.end(), TestResult(_os));
			back_ref(_os, suite, false);
		}
	}
};

// Individual tests result tables, iterator
//
struct HtmlOutput::SuiteTestResult
{
	ostream& _os;
	SuiteTestResult(ostream& os) : _os(os) {}
	void operator()(const SuiteInfo& si)
	{
		for_each(si._tests.begin(), si._tests.end(), TestResultAll(_os));
	}
};

/// Generates the HTML table. This function should only be called after
/// run(), when all tests have been executed.
///
/// \param os            Output stream.
/// \param incl_ok_tests Set if successful tests should be shown;
///                      false otherwise.
/// \param name          Name of generated report.
///
void
HtmlOutput::generate(ostream& os, bool incl_ok_tests, const string& name)
{
	ClassType 		type(_total_errors > 0 ? Error : Success);
	ostringstream 	ss;

	header(os, name);

	// Table: Summary
	//
	sub_title(os, "Summary", 2);
	table_header(os, TableClass_Summary, "Summary of test results");
	  table_tr_header(os);
	    table_entry(os, Title, "Tests", 30);
	    table_entry(os, Title, "Errors", 30);
	    table_entry(os, Title, "Success", 30);
	    table_entry(os, Title, "Time (s)", 10);
	  table_tr_footer(os);
	  table_tr_header(os);
	    ss.str(""), ss << _total_tests;
		    table_entry(os, type, ss.str(), 30);
		ss.str(""),  ss << _total_errors;
	    table_entry(os, type, ss.str(), 30);
		ss.str(""),  ss << correct(_total_tests, _total_errors) << "%";
	    table_entry(os, type, ss.str(), 30);
		ss.str(""), ss << _total_time;
	    table_entry(os, type, ss.str(), 10);
	  table_tr_footer(os);
	table_footer(os);
	os << "<hr />\n\n";

	// Table: Test suites
	//
	sub_title(os, "Test suites", 2);
	table_header(os, TableClass_Suites, "Test Suites");
	  table_tr_header(os);
	    table_entry(os, Title, "Name");
	    table_entry(os, Title, "Tests",   10);
	    table_entry(os, Title, "Errors",  10);
	    table_entry(os, Title, "Success",    10);
	    table_entry(os, Title, "Time (s)", 10);
	  table_tr_footer(os);
	  for_each(_suites.begin(), _suites.end(), SuiteRow(os));
	table_footer(os);
	os << "<hr />\n\n";

	// Individual tests tables
	//
	for_each(_suites.begin(), _suites.end(), TestSuiteRow(os, incl_ok_tests));
	os << "<hr />\n\n";

	// Individual tests result tables
	//
	if(_total_errors != 0)
	{
		sub_title(os, "Test results", 2);
		for_each(_suites.begin(), _suites.end(), SuiteTestResult(os));
		os << "<hr />\n\n";
	}
	// EOF
	//
	footer(os);
}

}}
